package uid

import (
	"errors"
	"fmt"
	"log/slog"
	"strconv"
	"sync"
	"time"
)

/**
* @package Gousing UidMachine
* @author ygzhang.cn<ygzhang.cn@msn.com>
* @site https://gitee.com/gousing
*
* UidMachine  64 Bit INT整型数值唯一ID实现库（至少可用1000年+）
* ------------------------------------------------------
* 基于 SnowFlake 算法设计编码
* ------------------------------------------------------
* 1、基于毫秒步进值生成,INT直接存储（数据表查询/排序/分页最优化））
* 2、基于毫秒步进值生成,方便业务拓展（分区分库分表)
* 3、自定义工作机Bit支持分布式部署，工作机ID使用评分法容错，当工作机维护或时间回拨时，ID启用一对一的AB互备机制
* 4、自定义自增序列Bit，应对突发业务量
* 5、ID可以反解析出毫秒时间戳、工作机编号位、自增序列位
* 6、工作机发生时间回拨记录日志,	并使用最后一个时间戳构造一个逻辑时间对象，确保时间戳递增(NextID递增)，即使时钟回拨后也能正常工作
*
* ------------------------------------------------------
* 编码规则
* ------------------------------------------------------
* 符号位(固定) + 压缩日期步进值位 + 日毫秒步进值位(固定27) + 集群工作机编号位 + 自增序列位
* 编码约定:
* DATE_MIX_BIT + WORK_BIT +  SEQUENCE_BIT == 36 Bit
* 默认编码:
* SIGN_BIT(1) + DATE_MIX_BIT(20) + MILLISECOND_BIT(27) + WORK_BIT(5) + SEQUENCE_BIT(11)
* ------------------------------------------------------
* 默认编码下（20 Bit压缩日期位） 可用1000年（64 Bit INT）, 21=>2000年, 22=>4000年, 23=>8000年
* +++第1年内（数字将快速接近16个INT数字，BASE_TINE从当前年yyyy-1-1）
* +++10年内（17个INT64数字）
* +++100年内（18个INT64数字）
* +++1000年内（19个IN64T数字）
* ------------------------------------------------------
* 默认编码下 （11 Bit自增序列位） 每台工作机/每毫秒最多支持  2^11=2048 个自增序列号 (0-2047),sequenceBit越大单机性能理论值越高
* 单机每毫秒最多可提供 2048个ID, QPS( 2048/ms 204.8万/s )，99.9999%的单机QPS性能都无法过万，加上网关及传输瓶颈，常见QPS只有几百几千
* 单机每秒最多可提供204.8万个ID
* 单机每天可提供的ID数量为1728亿
* ------------------------------------------------------
* 注意: 为了支持多机或多容器部署，涉及到时间计算统一做UTC+0000处理，与工作机时区无关
* ------------------------------------------------------
 */

const (
	// SignBit 默认符号位(固定)1 Bit(符号位，不能修改)
	//		- SignBit        int = 1
	// millisecondBit 默认日毫秒步进值位(固定27)
	// 		- 日毫秒时间步进值，不能修改，0-86400000 毫秒
	millisecondBit int = 27
	// DefaultDateMixBit 默认压缩日期步进值位, 存放实时日期压缩值之差, 日期压缩值=当前日期ZipDays - baseZipDays
	//		- 默认20 Bit (最小20), 默认编码下（20 Bit） 可用1000年（64 Bit INT）
	//    - [注意] dateMixBit + workerBit + sequenceBit 存储位之和必须为 36
	DefaultDateMixBit int = 20
	// DefaultworkerBit 默认集群工作机编号位
	//		- 默认 5 Bit(最小2), 支持 2^5=32台工作机 (16个A类ID<0-15>, 16个B类ID<16-31>), workerBit 越大工作机数量越多
	DefaultworkerBit int = 5
	// DefaultSequenceBit 默认自增序列位
	//		- 默认 11 Bit(最小6)  每台工作机/每毫秒最多支持 2^11=2048 个自增序列号 (0-2047),sequenceBit越大单机性能理论值越高
	DefaultSequenceBit int = 11
	// DefaultbaseZipDays 默认系统启用日期压缩值(年份中的第几天)
	//		- 默认 2023001 系统启用日期值(一年中的第几天), 天数固定为3位，不足前置补零, 最小值为1970001
	DefaultbaseZipDays int64 = 2023001
	// DefaultWorkerID 默认工作机ID
	DefaultWorkerID int64 = 0
	// DefaultWorkerMax 默认工作机配置工作机位最大值(ID)
	DefaultWorkerMax int64 = -1 ^ (-1 << DefaultworkerBit)
	// DefaultSequenceMax 默认工作机配置自增序列位最大值
	DefaultSequenceMax int64 = -1 ^ (-1 << DefaultSequenceBit)
)

// uidMachine 工作机
type uidMachine struct {
	// -------------------------
	// 自定义工作机配置
	// -------------------------
	// baseZipDays 系统启用日期压缩值(年份中的第几天)最小值 2022001
	baseZipDays int64 // 默认 = 2022001
	// dateMixBit 默认 20 Bit(20-23,最小20，最大23)，存放实时日期压缩值之差, 日期压缩值=当前日期 todayZipDays - baseZipDays
	// 	 - 20Bit
	dateMixBit int // 默认 = 20
	// workerBit 默认 5 Bit, workerBit 越大工作机数量越多
	// 	 - 工作机ID使用平分容错，当工作机维护或时间回拨时，可以启用一对一的AB互备机制
	// 	 - 2^5=32台工作机 (16个A类ID<0-15>, 16个B类ID<16-31>), workerBit 越大工作机数量越多
	// 	 - 0号工作机维护或时间回拨时时，可调整为16号
	// 	 - 15号工作机维护或时间回拨时时，可调整为31号
	workerBit int

	//sequenceBit 默认 11 Bit(最小6)  每台工作机/每毫秒最多支持 2^11=2048 个自增序列号 (0-2047),sequenceBit越大单机性能理论值越高
	// 	 - 单机每毫秒最多可提供 2048个ID, QPS( 2048/ms 204.8万/s )，99.9999%的单机QPS性能都无法过万，加上网关及传输瓶颈，常见QPS只有几百几千
	// 	 - 单机每秒最多可提供204.8万个ID
	// 	 - 单机每天可提供的ID数量为1728亿
	sequenceBit int
	// ---------------------------------------------------
	// 下面为自动计算的位移值
	// ---------------------------------------------------
	//leftWorker
	//sequenceBit
	leftWorker int //= sequenceBit
	//leftMillisecond
	//sequenceBit + workerBit
	leftMillisecond int //= sequenceBit + workerBit
	//leftDateMix
	//sequenceBit + workerBit + millisecondBit
	leftDateMix int //= sequenceBit + workerBit + millisecondBit
	//LeftSign
	//sequenceBit + workerBit + millisecondBit + dateMixBit
	LeftSign int //= sequenceBit + workerBit + millisecondBit + dateMixBit

	// workerMax 最大机器编号
	workerMax int64 //= -1 ^ (-1 << workerBit)
	// sequenceMax 最大序列编号
	sequenceMax int64 //= -1 ^ (-1 << sequenceBit)
	// -------------------------
	// 运行时数据
	// -------------------------
	// mu 加锁保证make入口的唯一性
	mu sync.Mutex
	// workerID 当前工作机ID
	workerID int64
	// todayZipDays 当日日期压缩值
	todayZipDays int64
	// todayStartTimestamp 当日日期开始时间戳(0点毫秒)
	todayStartTimestamp int64
	// lastTimestamp 当前运行时时间戳(毫秒)
	lastTimestamp int64
	// lastSequence 当前运行时序列号
	lastSequence int64
	// isClockBackward 是否发生了时钟回拨
	isClockBackward bool
	// isClockBackLog  是否记录了本次时钟回拨的相关日志(避免重复记录大量日志)
	isClockBackLog bool
}

type UidMachine interface {
	// NextID 生成一个新的UID
	//   - 返回值是int64类型的UID
	NextID() int64
	// GetWorkerID 获取当前工作机ID
	GetWorkerID() int64
	// GetWorkerMax 获取当前工作机配置工作机位最大值(ID)
	GetWorkerMax() int64
	// GetSequenceMax 获取当前工作机自增序列位最大值
	GetSequenceMax() int64
	// GetLastTimestamp 获取当前工作机运行时时间戳(毫秒)
	GetLastTimestamp() int64
	// SetWorkerID 更换当前工作机ID，通常用于处理时间回拨故障，切换到互备工作机ID
	SetWorkerID(id int64) error
	// SetLastTimestamp 设置当前工作机运行时时间戳(毫秒), 通常用于处理时间回拨故障，当服务器(工作机)重新启动时，设置上次运行时间回拨故障时最后的时间戳
	SetLastTimestamp(ts int64) error
	// IsClockBackward 单前是否正在发生时钟回拨故障
	IsClockBackward() bool
	// Parse 解析Int64格式的UID
	Parse(uid int64) (*UidInfo, error)
}

// NewUidMachine 创建一个新的工作机
//   - dateMixBit 压缩日期步进值位: 默认20 Bit (最小20)，存放实时日期压缩值之差, 日期压缩值=当前日期ZipDays - baseZipDays
//   - workerBit 工作机编号位:默认 5 Bit(最小2), 支持 2^5=32台工作机 (16个A类ID<0-15>, 16个B类ID<16-31>), workerBit 越大工作机数量越多
//   - sequenceBit 自增序列位: 默认 11 Bit(最小6)  每台工作机/每毫秒最多支持 2^11=2048 个自增序列号 (0-2047),sequenceBit越大单机性能理论值越高
//   - [注意] dateMixBit + workerBit + sequenceBit 存储位之和必须为 36
//   - workerId 工作机ID
//   - baseZipDays 系统启用日期压缩值: 默认 2023001 系统启用日期值(一年中的第几天), 天数固定为3位，不足前置补零, 最小值为1970001
//   - [注意] 投入生产环境后，不能修改dateMixBit/workerBit/sequenceBit/baseZipDays
func NewUidMachine(dateMixBit, workerBit, sequenceBit int, workerID, baseZipDays int64) (UidMachine, error) {
	if dateMixBit < 20 {
		return nil, errors.New("uidMachine: NewUidMachine dateMixBit must be > 20")
	}
	if workerBit < 2 {
		return nil, errors.New("uidMachine: NewUidMachine workerBit must be > 2")
	}
	if sequenceBit < 6 {
		return nil, errors.New("uidMachine: NewUidMachine sequenceBit must be > 6")
	}
	if dateMixBit+workerBit+sequenceBit != 36 {
		return nil, errors.New("uidMachine: NewUidMachine dateMixBit + workerBit + sequenceBit must be 36")
	}
	var workerMax int64 = -1 ^ (-1 << workerBit)
	if workerID < 0 || workerID > workerMax {
		return nil, fmt.Errorf("uidMachine: NewUidMachine workerId must be 0 <= workerId <= %d", workerMax)
	}
	if baseZipDays < 1970001 {
		return nil, errors.New("uidMachine: NewUidMachine baseZipDays must be > 1970001")
	}
	now := time.Now()
	year, month, day := now.UTC().Date()
	todayTime := time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
	todayZipDays := int64(todayTime.Year()*1000 + todayTime.YearDay())
	if baseZipDays > todayZipDays {
		return nil, fmt.Errorf("uidMachine: NewUidMachine baseZipDays must be <= %d", todayZipDays)
	}
	machine := &uidMachine{
		workerID:        workerID,    // Int64 便于运行时直接参与计算
		baseZipDays:     baseZipDays, // Int64 便于运行时直接参与计算
		dateMixBit:      dateMixBit,
		workerBit:       workerBit,
		sequenceBit:     sequenceBit,
		leftWorker:      sequenceBit,
		leftMillisecond: sequenceBit + workerBit,
		leftDateMix:     sequenceBit + workerBit + millisecondBit,
		LeftSign:        sequenceBit + workerBit + millisecondBit + dateMixBit,
		workerMax:       -1 ^ (-1 << workerBit),
		sequenceMax:     -1 ^ (-1 << sequenceBit),
	}
	machine.updateTodayTime(&now)
	return machine, nil
}

// NextID 生成一个新的UID
//   - 返回值是int64类型的UID
//   - 入口方法加锁保证线程并发的安全
func (u *uidMachine) NextID() int64 {
	defer func() {
		if err := recover(); err != nil {
			GetLogger().Error("UidMachine: Recovered", slog.Int64("workerId", u.workerID), slog.String("error", fmt.Sprintf("%v", err)))
		}
	}()
	u.mu.Lock()
	defer u.mu.Unlock()
	todayStartTimestamp, nowTimestamp := u.getTimestamp()
	// 同一毫秒
	if nowTimestamp == u.lastTimestamp {
		// 更新序列号
		u.lastSequence = (u.lastSequence + 1) & u.sequenceMax
		if u.lastSequence == 0 {
			// 等待下一毫秒
			for nowTimestamp <= u.lastTimestamp {
				time.Sleep(time.Millisecond)
				todayStartTimestamp, nowTimestamp = u.getTimestamp()
			}
		}
	} else {
		u.lastSequence = 0
	}
	u.lastTimestamp = nowTimestamp
	return ((u.todayZipDays - u.baseZipDays) << u.leftDateMix) |
		((nowTimestamp - todayStartTimestamp) << u.leftMillisecond) |
		(u.workerID << u.leftWorker) |
		u.lastSequence
}

func (u *uidMachine) getTimestamp() (todayStartTimestamp int64, nowTimestamp int64) {
	now := getNowTime()
	nowTimestamp = (*now).UnixMilli()
	u.isClockBackward = nowTimestamp < u.lastTimestamp
	if u.isClockBackward {
		// 发生时钟回拨
		if !u.isClockBackLog {
			// 仅记录一次日志
			u.isClockBackLog = true
			GetLogger().Error("UidMachine: Clock is moving backwards", slog.Int64("workerId", u.workerID), slog.Int64("millisecond", u.lastTimestamp-nowTimestamp))
		}
		// 使用最后一个时间戳构造一个逻辑时间对象
		// 确保工作机的时间戳是递增的(NextID是递增的)，即使时钟回拨后也能正常工作
		u.lastTimestamp++
		*now = time.UnixMilli(u.lastTimestamp).UTC()
		nowTimestamp = (*now).UnixMilli()
	} else {
		u.isClockBackLog = false
	}
	if u.todayZipDays != int64((*now).Year()*1000+(*now).YearDay()) {
		u.updateTodayTime(now)
	}
	todayStartTimestamp = u.todayStartTimestamp
	return
}
func (u *uidMachine) updateTodayTime(now *time.Time) {
	year, month, day := (*now).Date()
	todayTime := time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
	u.todayStartTimestamp = todayTime.UnixMilli()
	u.todayZipDays = int64(todayTime.Year()*1000 + todayTime.YearDay())
}

// GetWorkerID 获取工作机ID
func (u *uidMachine) GetWorkerID() int64 {
	return u.workerID
}

// GetWorkerMax 获取工作机位最大值
func (u *uidMachine) GetWorkerMax() int64 {
	return u.workerMax
}

// GetSequenceMax 获取工作机自增序列位最大值
func (u *uidMachine) GetSequenceMax() int64 {
	return u.sequenceMax
}

// GetLastTimestamp 获取当前工作机运行时时间戳(毫秒)
//   - 返回值是int64类型的时间戳(毫秒)
//   - 未生产任何NextID时返回0
func (u *uidMachine) GetLastTimestamp() int64 {
	return u.lastTimestamp
}

// SetWorkerID 更换当前工作机ID，通常用于处理时间回拨故障，切换到互备工作机ID
func (u *uidMachine) SetWorkerID(id int64) error {
	if id < 0 || id > u.workerMax {
		return errors.New("uidMachine: SetWorkerID Error: The WorkerId is invalid")
	}
	u.mu.Lock()
	u.workerID = id
	u.mu.Unlock()
	return nil
}

// SetLastTimestamp 设置当前工作机运行时时间戳(毫秒), 通常用于处理时间回拨故障，当服务器(工作机)重新启动时，设置上次运行时间回拨故障时最后的时间戳
func (u *uidMachine) SetLastTimestamp(ts int64) error {
	if ts < 0 {
		return errors.New("uidMachine:SetLastTimestamp error: The timestamp is invalid")
	} else if ts > time.Now().UnixMilli() {
		return errors.New("uidMachine:SetLastTimestamp error: The timestamp cannot be greater than the current time")
	}
	u.mu.Lock()
	u.lastTimestamp = ts
	u.mu.Unlock()
	return nil
}

// IsClockBackward 单前是否正在发生时钟回拨故障
func (u *uidMachine) IsClockBackward() bool {
	u.mu.Lock()
	defer u.mu.Unlock()
	return u.isClockBackward
}

type UidInfo struct {
	WorkerId   int64  // 工作机ID
	Sequence   int64  // 序列号
	ServerTime string // 服务器时间(服务器时区)
	UnixMilli  int64  // 毫秒时间戳
}

// Parse 解析UID信息
//
//	注意:
//	- 未发生时间回拨，解析出来的时间信息ID是创建的时间信息
//	- 如发生时间回拨，解析出来的时间信息是发生回拨时逻辑时间信息
func (m *uidMachine) Parse(uid int64) (uidInfo *UidInfo, err error) {
	if uid < 1000000 {
		return nil, errors.New("uidMachine: Parse Error: Uid is invalid")
	}
	idBin := strconv.FormatInt(uid, 2)
	idLen := len(idBin)
	sequenceIndex := idLen - m.sequenceBit

	var (
		Sequence    int64
		WorkerId    int64
		millisecond int64
	)

	if Sequence, err = strconv.ParseInt(idBin[sequenceIndex:sequenceIndex+m.sequenceBit], 2, 32); err != nil {
		return nil, errors.New("uidMachine: Parse Error: Sequence")
	}
	if Sequence < 0 || Sequence > m.sequenceMax {
		return nil, errors.New("uidMachine: Parse Error: Sequence")
	}
	uidInfo = new(UidInfo)
	uidInfo.Sequence = Sequence

	workerIndex := idLen - m.sequenceBit - m.workerBit
	if WorkerId, err = strconv.ParseInt(idBin[workerIndex:workerIndex+m.workerBit], 2, 10); err != nil {
		return nil, errors.New("uidMachine: Parse Error: WorkerId")
	}
	if WorkerId != m.workerID {
		return nil, errors.New("uidMachine: Parse Error: WorkerId")
	}
	uidInfo.WorkerId = WorkerId

	millisecondIndex := idLen - m.sequenceBit - m.workerBit - millisecondBit
	if millisecond, err = strconv.ParseInt(idBin[millisecondIndex:millisecondIndex+millisecondBit], 2, 64); err != nil {
		return nil, errors.New("uidMachine: Parse Error: Millisecond")
	}
	if millisecond < 0 || millisecond > 86400000 {
		return nil, errors.New("uidMachine: Parse Error: Millisecond")
	}

	//解析年月日
	var dateMix int64
	if dateMix, err = strconv.ParseInt(idBin[0:millisecondIndex], 2, 32); err != nil {
		return nil, errors.New("uidMachine: Parse Error: dateMix")
	}
	dateString := strconv.FormatInt(dateMix+m.baseZipDays, 10)
	dateYear, _ := strconv.ParseInt(dateString[0:len(dateString)-3], 10, 32) //除去后三位的是年份
	dateDays, _ := strconv.ParseInt(dateString[len(dateString)-3:], 10, 32)  //后三位是日历第X天

	timeNow := time.Now().UTC()
	if dateYear < 1970 || dateYear > int64(timeNow.Year()) || dateDays < 1 || dateDays > 366 {
		return nil, errors.New("uidMachine: Parse Error: dateMix")
	}
	//处理压缩日期与毫秒步进值，转换出Unix时间戳(UTC)及毫秒数值
	zeroTime := time.Date(int(dateYear), 1, 1, 0, 0, 0, 0, time.UTC)
	zeroTime = zeroTime.Add(time.Duration((dateDays-1)*86400) * time.Second)
	zeroTime = zeroTime.Add(time.Duration(millisecond) * time.Millisecond)
	uidInfo.ServerTime = zeroTime.In(time.Local).Format("2006-01-02T15:04:05.000 -07:00")
	uidInfo.UnixMilli = zeroTime.UnixMilli()
	return uidInfo, nil
}
